%%%-------------------------------------------------------------------
%%% @author wukai
%%% @copyright (C) 2020, <COMPANY>
%%% @doc
%%%
%%% @end
%%% Created : 19. 10月 2020 11:14 上午
%%%-------------------------------------------------------------------
-module(himq_mnesia).
-author("wukai").
-include("himq.hrl").
-include_lib("stdlib/include/qlc.hrl").
%% API
-export([init/0, count/1, sequence/1, write/1, page/4, delete/2, record_exist/2,
  page_where/5, token_exist/2, auth_admin/2, read/2, eenv_users/0, eenv_setting/0]).
init() ->
  io:setopts({encoding, unicode}),
  create_schema(),
  mnesia:start(),
  mnesia:transaction(fun() -> mnesia:clear_table(?TABLE_UNDELIVERED) end),
  create_tables([
    {?TABLE_SEQUENCE, record_info(fields, ?TABLE_SEQUENCE), set},
    {?TABLE_ADMIN, record_info(fields, ?TABLE_ADMIN), set},
    {?TABLE_APPLICATION, record_info(fields, ?TABLE_APPLICATION), set},
    {?TABLE_COMMAND, record_info(fields, ?TABLE_COMMAND), set},
    {?TABLE_MQTT_USER, record_info(fields, ?TABLE_MQTT_USER), set},
    {?TABLE_DEVICE, record_info(fields, ?TABLE_DEVICE), set},
    {?TABLE_RETAIN, record_info(fields, ?TABLE_RETAIN), set},
    {?TABLE_PUSH, record_info(fields, ?TABLE_PUSH), set},
    {?TABLE_SETTING, record_info(fields, ?TABLE_SETTING), set},
    {?TABLE_UNDELIVERED, record_info(fields, ?TABLE_UNDELIVERED), set},
    {?TABLE_LINK, record_info(fields, ?TABLE_LINK), bag}
  ]),

  init_setting(),
  init_app(),
  init_admin(),

  mnesia:wait_for_tables([?TABLE_SEQUENCE, ?TABLE_ADMIN, ?TABLE_APPLICATION, ?TABLE_COMMAND], 10000),

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  eenv_users(),
  eenv_setting()
.


sequence(Table) -> mnesia:dirty_update_counter(?TABLE_SEQUENCE, Table, 1).
count(Table) -> mnesia:table_info(Table, size).

create_schema() ->
  application:set_env(mnesia, dir, "data"),
  case mnesia:system_info(use_dir) of
    true ->
      lager:info("database is exists  !!!");
    _ ->
      case mnesia:create_schema([node()]) of
        ok -> lager:info("database created success  !!!");
        _ -> lager:info("database created error  !!!"), exit(normal)
      end
  end.
create_tables(List) ->
  lists:foreach(fun({TableName, Attr, Type}) ->
    case lists:member(TableName, mnesia:system_info(tables)) of
      true ->
        lager:info("table ~p is exists in mnesia !!!", [TableName]);
      _ ->
        create_table(TableName, Attr, Type)
    end
                end, List).

create_table(Table, Attr, Type) ->
  mnesia:create_table(Table, [{attributes, Attr}, {type, Type}, {disc_copies, [node()]}]).
%初始化默认app
init_app() ->
  AppId = himq:md5("admin"),
  Debug = #himq_auth_app{key = AppId, create_at = himq:timestamp(), updated_at = himq:timestamp(), creator = <<"system">>,
    remark = <<"system init">>, enable = true, name = <<"系统调试"/utf8>>},
  write(Debug).

%%初始化配置表
init_setting() ->
  List = [
    #himq_setting{key = ?SETTING_BASE_KEY, value = #{auth_way=><<"user_pass">>, offline=>[<<"save">>, <<"publish">>]}},
    #himq_setting{key = ?SETTING_PUSH_KEY, value = #{push_qos=><<"1">>, push_save=><<"0">>}}
  ],
  lists:foreach(fun(E) ->
    case record_exist(?TABLE_SETTING, E#himq_setting.key) of
      true -> ok;
      false -> write(E)
    end
                end, List).


%%初始化默认管理员
init_admin() ->
  List = himq_eenv:get(?HIMQ, ?LISTENER_HTTP_ADMIN, [#{username=>admin, password=>admin}]),
  lists:foreach(fun(#{username:=Username, password:=Password} = E) ->
    lager:info("config user:~p", [E]),
    Username1 = himq:to_binary(Username),
    Password1 = himq:to_binary(Password),
    case record_exist(?TABLE_ADMIN, Username1) of
      true -> ok;
      false ->
        Md5 = himq:md5(Password1),
        write(#himq_admin{username = Username1, password = himq:to_binary(Md5)})
    end end, List).

eenv_users() ->
  Fun = fun() -> mnesia:foldl(fun(E, Acc) ->
    Acc ++ [{E#himq_mqtt_user.username, E#himq_mqtt_user.password}]
                              end, [], ?TABLE_MQTT_USER) end,
  {atomic, List} = mnesia:transaction(Fun),
  lager:info("eenv auth users:~p", [List]),
  himq_eenv:set(himq_app, users, List).

eenv_setting() ->
  Fun = fun() -> mnesia:foldl(fun(E, Acc) ->
    Acc ++ [E]
                              end, [], ?TABLE_SETTING) end,
  {atomic, List} = mnesia:transaction(Fun),
  lager:info("eenv settings:~p", [List]),
  lists:foreach(fun(E) ->
    himq_eenv:set(himq_app, E#himq_setting.key, E#himq_setting.value)
                end, List).

delete(Table, Key) ->
  mnesia:transaction(fun() -> mnesia:delete({Table, Key}) end).

read(Table, Key) ->
  mnesia:transaction(fun() ->
    mnesia:read({Table, Key})
                     end).

write(Item) -> mnesia:transaction(
  fun() -> mnesia:write(Item) end).

page(Table, 1, PageSize, Fun) ->
  F = fun() ->
    QH = qlc:q([Item || Item <- mnesia:table(Table)]),
    Qc = qlc:cursor(QH),
    qlc:next_answers(Qc, PageSize)
      end,
  page_item(1, count(Table), mnesia:transaction(F), Fun);

page(Table, Page, PageSize, Fun) when Page > 1 ->
  F = fun() ->
    QH = qlc:q([Item || Item <- mnesia:table(Table)]),
    Qc = qlc:cursor(QH),
    qlc:next_answers(Qc, (Page - 1) * PageSize),
    qlc:next_answers(Qc, PageSize)
      end,
  page_item(Page, count(Table), mnesia:transaction(F), Fun).

page_where(Table, 1, PageSize, Fun, QH) ->
  F = fun() ->
    Qc = qlc:cursor(QH),
    qlc:next_answers(Qc, PageSize)
      end,
  page_item(1, count(Table), mnesia:transaction(F), Fun);

page_where(Table, Page, PageSize, Fun, QH) ->
  F = fun() ->
    Qc = qlc:cursor(QH),
    qlc:next_answers(Qc, (Page - 1) * PageSize),
    qlc:next_answers(Qc, PageSize)
      end,
  page_item(Page, count(Table), mnesia:transaction(F), Fun).

page_info(Page, Size, []) -> #{<<"page">>=>Page,
  <<"count">>=>Size,
  <<"items">>=>[]
};

page_info(Page, Size, List) -> #{<<"page">>=>Page,
  <<"count">>=>Size,
  <<"items">>=>List
}.

record_exist(Table, Key) ->
  case mnesia:transaction(fun() -> mnesia:read({Table, Key}) end) of
    {atomic, List} when is_list(List), length(List) =:= 1 -> true;
    _ -> false
  end.

token_exist(Username, Token) ->
  lager:info("Username ~p,Token:~p", [Username, Token]),
  F = fun() ->
    QH = qlc:q([Admin || Admin <- mnesia:table(?TABLE_ADMIN),
      Admin#himq_admin.username =:= Username,
      Admin#himq_admin.token =:= Token,
      Admin#himq_admin.expire > himq:timestamp()
    ]),
    Qc = qlc:cursor(QH),
    qlc:next_answers(Qc) end,
  case mnesia:transaction(F) of
    {atomic, List} when is_list(List), length(List) =:= 1 -> true;
    Err ->
      lager:info("token_exist ~p", [Err]),
      false
  end.

auth_admin(Username, Password) ->
  lager:info("password:~p,username ~p", [himq:md5(Password), Username]),

  F = fun() ->
    QH = qlc:q([Admin || Admin <- mnesia:table(?TABLE_ADMIN),
      Admin#himq_admin.username =:= Username,
      Admin#himq_admin.password =:= list_to_binary(himq:md5(Password))
    ]),
    Qc = qlc:cursor(QH),
    qlc:next_answers(Qc) end,
  case mnesia:transaction(F) of
    {atomic, List} when is_list(List), length(List) =:= 1 -> true;
    Err ->
      lager:info("auth_admin ~p", [Err]),
      false
  end.

page_item(1, Size, Result, Fun) ->
  %lager:info("page item ~p,", [Result]),
  case Result of
    {atomic, []} -> page_info(1, Size, []);
    {atomic, List} ->
      page_info(1, Size, lists:map(Fun, List));
    E ->
      lager:info("page error ~p ", [E]),
      page_info(1, Size, [])
  end;
page_item(Page, Size, Result, Fun) ->
  %%lager:info("page item ~p,", [Result]),
  case Result of
    {atomic, []} -> page_info(Page, Size, []);
    {atomic, List} ->
      page_info(Page, Size, lists:map(Fun, List));
    E ->
      lager:info("page error ~p ", [E]),
      page_info(Page, Size, [])
  end
.
upgrade() ->
  Fun = fun(X) ->
    case X of
      {himq_command, Id, Name, Type, Packet, AddTime} ->
        {himq_command, Id, Name, Type, "topic", Packet, AddTime};
      _ ->
        X
    end
        end,
  NewAttr = [id, name, type, topic, packet, add_time],
  mnesia:transform_table(?TABLE_COMMAND, Fun, NewAttr).
